package org.smartboot.compare;

import org.smartboot.compare.array.BooleanArrayComparator;
import org.smartboot.compare.array.BooleanPrimitiveArrayComparator;
import org.smartboot.compare.array.ByteArrayComparator;
import org.smartboot.compare.array.BytePrimitiveArrayComparator;
import org.smartboot.compare.array.CharacterArrayComparator;
import org.smartboot.compare.array.CharacterPrimitiveArrayComparator;
import org.smartboot.compare.array.DoubleArrayComparator;
import org.smartboot.compare.array.DoublePrimitiveArrayComparator;
import org.smartboot.compare.array.FloatArrayComparator;
import org.smartboot.compare.array.FloatPrimitiveArrayComparator;
import org.smartboot.compare.array.IntArrayComparator;
import org.smartboot.compare.array.IntegerArrayComparator;
import org.smartboot.compare.array.LongArrayComparator;
import org.smartboot.compare.array.LongPrimitiveArrayComparator;
import org.smartboot.compare.array.ObjectArrayComparator;
import org.smartboot.compare.array.ShortArrayComparator;
import org.smartboot.compare.array.ShortPrimitiveArrayComparator;
import org.smartboot.compare.comparator.AbstractComparator;
import org.smartboot.compare.comparator.AttributesComparator;
import org.smartboot.compare.comparator.BooleanComparator;
import org.smartboot.compare.comparator.Comparator;
import org.smartboot.compare.comparator.DateComparator;
import org.smartboot.compare.comparator.DispatcherComparator;
import org.smartboot.compare.comparator.ListComparator;
import org.smartboot.compare.comparator.MapComparator;
import org.smartboot.compare.comparator.SetComparator;
import org.smartboot.compare.comparator.StringComparator;
import org.smartboot.compare.comparator.TagComparator;
import org.smartboot.compare.utils.ArgumentUtils;
import org.smartboot.compare.utils.InternalClassUtils;

import java.util.ArrayList;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;

/**
 *
 * @author qinluo
 * @version 1.0.0
 * @since 2019-05-27 17:52
 */
@SuppressWarnings({"unchecked", "rawtypes"})
public class ComparatorRegister {

    /**
     * All direct comparator holder.
     */
    private static final Map<Class, Comparator> COMPARATOR_MAP = new HashMap<>(32);

    /**
     * Customized comparator used in Map/Fields.
     */
    private static final Map<NameType, AbstractComparator> NAME_TYPE_COMPARATOR = new HashMap<>(32);

    /**
     * Dispatcher Comparator instance.
     */
    private static final DispatcherComparator DISPATCHER_COMPARATOR = new DispatcherComparator();

    static {
        register(String.class, new StringComparator());
        register(Boolean.class, new BooleanComparator());
        register(List.class, new ListComparator());
        COMPARATOR_MAP.put(Object.class, DISPATCHER_COMPARATOR);
        register(Map.class, new MapComparator());
        register(Set.class, new SetComparator());
        register(Date.class, new DateComparator());
        register(int[].class, new IntArrayComparator());
        register(Integer[].class, new IntegerArrayComparator());
        register(byte[].class, new BytePrimitiveArrayComparator());
        register(Byte[].class, new ByteArrayComparator());
        register(short[].class, new ShortPrimitiveArrayComparator());
        register(Short[].class, new ShortArrayComparator());
        register(long[].class, new LongPrimitiveArrayComparator());
        register(Long[].class, new LongArrayComparator());
        register(boolean[].class, new BooleanPrimitiveArrayComparator());
        register(Boolean[].class, new BooleanArrayComparator());
        register(char[].class, new CharacterPrimitiveArrayComparator());
        register(Character[].class, new CharacterArrayComparator());
        register(float[].class, new FloatPrimitiveArrayComparator());
        register(Float[].class, new FloatArrayComparator());
        register(double[].class, new DoublePrimitiveArrayComparator());
        register(Double[].class, new DoubleArrayComparator());
        register(Object[].class, new ObjectArrayComparator());

        register(NameType.of(String.class, "attribute"), AttributesComparator.getInstance());
        register(NameType.of(String.class,"attributes"), AttributesComparator.getInstance());
        register(NameType.of(String.class, "tag"), TagComparator.getInstance());
        register(NameType.of(String.class, "tags"), TagComparator.getInstance());
    }

    public static Comparator register(NameType nameType, AbstractComparator<?> comparator) {
        ArgumentUtils.notNull(nameType, "register namedType must not be null!");
        ArgumentUtils.notNull(comparator, "register comparator must not be null!");

        Class<?> accept = comparator.getType();
        Class<?> actual = nameType.getType();

        if (actual != accept && !accept.isAssignableFrom(actual)) {
            throw new IllegalArgumentException("register namedType must match compare!");
        }

        return NAME_TYPE_COMPARATOR.put(nameType, comparator);
    }

    public static Comparator<?> findComparator(NameType nameType) {
        ArgumentUtils.notNull(nameType, "register namedType must not be null!");
        if (nameType.type.isPrimitive()) {
            nameType.type = InternalClassUtils.getWrapper(nameType.type);
        }

        Map<NameType, AbstractComparator> allComparators = new HashMap<>(NAME_TYPE_COMPARATOR);
        allComparators.putAll(ThreadLocalComparatorRegister.getRegisteredNameTypeComparators());
        AbstractComparator direct = allComparators.get(nameType);
        if (direct != null) {
            return direct;
        }

        NameType candidate = null;
        // 找到匹配nameType情况下最贴近type的比较器， 比如 i-List 和 i-Collect, 请求i-LinkedList 返回 i-List
        for (Entry<NameType, AbstractComparator> entry : allComparators.entrySet()) {
            NameType key = entry.getKey();
            if (!key.match(nameType)) {
                continue;
            }

            if (direct == null) {
                direct = entry.getValue();
                candidate = key;
            } else if (candidate.getType().isAssignableFrom(nameType.getType())) {
                candidate = key;
                direct = entry.getValue();
            }
        }

        return direct;
    }


    public static Comparator register(Class<?> clz, Comparator<?> compare) {
        ArgumentUtils.notNull(clz, "register class must not be null!");
        if (clz == Object.class) {
            throw new IllegalArgumentException("Object.class must not override.");
        }

        if (compare instanceof AbstractComparator) {
            AbstractComparator<?> abstractCompare = (AbstractComparator<?>) compare;
            Class<?> accept = abstractCompare.getType();

            if (clz != accept && !accept.isAssignableFrom(clz)) {
                throw new IllegalArgumentException("register class must be same type with compare#getType!");
            }
        }

        return COMPARATOR_MAP.put(clz, compare);
    }

    /**
     * Unregister comparator with specified class.
     */
    public static Comparator unregister(Class<?> clazz) {
        return COMPARATOR_MAP.remove(clazz);
    }

    /**
     * Unregister comparator with namedType.
     */
    public static Comparator unregister(NameType nameType) {
        return NAME_TYPE_COMPARATOR.remove(nameType);
    }

    public static <T> Comparator<T> findComparator(Class<T> clz) {
        ArgumentUtils.notNull(clz, "not null");
        Map<Class, Comparator> allComparators = new HashMap<>(COMPARATOR_MAP);
        allComparators.putAll(ThreadLocalComparatorRegister.getRegisteredComparators());

        if (clz.isPrimitive()) {
            clz = InternalClassUtils.getWrapper(clz);
        }

        Comparator<T> compare = allComparators.get(clz);
        if (compare != null) {
            return compare;
        }

        Comparator<T> bestMatch = allComparators.get(Object.class);
        Class<?> bestMatchKey = Object.class;

        //兼容集合类比较，统一以集合类的父类进行比较，但是要排除Object
        for (Map.Entry<Class, Comparator> entry : allComparators.entrySet()) {
            if (entry.getKey() == null
                    || !entry.getKey().isAssignableFrom(clz)
                    || entry.getKey() == Object.class) {
                continue;
            }

            if (bestMatchKey == Object.class || bestMatchKey.isAssignableFrom(entry.getKey())) {
                bestMatch = entry.getValue();
                bestMatchKey = entry.getKey();
            }
        }

        return bestMatch;
    }

    public static <T> Comparator<T> getSystemComparator(Class<T> clz) {
        return COMPARATOR_MAP.get(clz);
    }

    public static <T> Comparator<T> dispatcherComparator() {
        return (Comparator<T> )DISPATCHER_COMPARATOR;
    }

    public static List<Object> getAllRegisterKeys() {
        List<Object> keys = new ArrayList<>();
        keys.addAll(COMPARATOR_MAP.keySet());
        keys.addAll(NAME_TYPE_COMPARATOR.keySet());
        return keys;
    }

}
